package com.codefactory.generator.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.codefactory.generator.config.GeneratorProperty;
import com.codefactory.generator.model.CodeTemplateMapping;
import com.codefactory.generator.model.FTLDataModel;
import com.codefactory.generator.model.TableColumn;
import com.codefactory.generator.model.TableInfo;
import com.codefactory.generator.param.*;
import com.codefactory.generator.utils.DatabaseUtil;
import com.codefactory.generator.utils.FreemarkerUtil;
import com.codefactory.generator.utils.TableColumnUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.sql.DatabaseMetaData;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class GenService {
    @Autowired
    private DatabaseUtil databaseUtil;
    @Autowired
    private GeneratorProperty generatorProperty;
    @Autowired
    private CodeGenerator codeGenerator;

    /**
     * 生成代码入口
     *
     * @param genParam
     * @param conf
     * @throws Exception
     */
    public void genCode(CodeGenParam genParam, ProjectConf conf) throws Exception {
        List<CodeGenParam> paramList = new LinkedList<>();
        paramList.add(genParam);
        genCode(paramList, conf);
    }

    public void genCode(List<CodeGenParam> paramList, ProjectConf conf) throws Exception {
        if (paramList == null || paramList.isEmpty()) return;
        checkProjectConf(conf);
        List<String> tableList = paramList.stream().map(CodeGenParam::getTableName).collect(Collectors.toList());
        Map<String, List<TableColumn>> listMap = databaseUtil.getColumnListForTables(tableList);
        List<FTLDataModel> modelList = getAllTableDataModel(conf, paramList, listMap);
        this.genCodeList(modelList, conf);
    }

    protected static void checkProjectConf(ProjectConf conf) throws Exception {
        if (conf.pathConf == null) {
            if (StringUtils.isBlank(conf.targetPath)) throw new Exception("生成目标路径为空，需要设置pathConf或者targetPath");
            conf.pathConf = WritePathConfFactory.createDefault(conf.targetPath);
        }
        if (conf.packageConf == null) {
            conf.packageConf = PackageConfFactory.createDefault(conf);
        }
    }

    public void generate(ProjectConf conf, File tableNameFile) throws Exception {
        List<String> lines = FileUtils.readLines(tableNameFile, "UTF-8");
        generate(conf, lines);
    }

    public void generate(ProjectConf conf, String... tables) throws Exception {
        List<String> list = new LinkedList<>();
        for (String s : tables) list.add(s);
        generate(conf, list);
    }

    public void generate(ProjectConf conf, List<String> tableList) throws Exception {
        if (tableList == null || tableList.isEmpty()) return;
        Iterator<String> iterator = tableList.iterator();
        while (iterator.hasNext()) {
            if (StringUtils.isBlank(iterator.next())) iterator.remove();
        }
        if (tableList.isEmpty()) return;
        //
        checkProjectConf(conf);
        //
        Map<String, String> commentMap = databaseUtil.getTableCommentMap(tableList);
        Map<String, List<TableColumn>> listMap = databaseUtil.getColumnListForTables(tableList);

        List<CodeGenParam> paramList = new LinkedList<>();
        for (String s : tableList) {
            CodeGenParam param = CodeGenParam.of(s, StringUtils.defaultString(commentMap.get(s)));
            paramList.add(param);
        }
        List<FTLDataModel> modelList = getAllTableDataModel(conf, paramList, listMap);
        this.genCodeList(modelList, conf);
    }

    private void genCodeList(List<FTLDataModel> modelList, ProjectConf conf) throws Exception {
        File ftlDirFile;
        if (StringUtils.isBlank(conf.templatePath)) {
            boolean isClasspath = StringUtils.startsWithIgnoreCase(generatorProperty.getTemplatePath(), "classpath:");
            if (isClasspath) {
                ftlDirFile = FileUtils.getFile(ResourceUtils.getFile(generatorProperty.getTemplatePath()), conf.templateClassify);
            } else {
                ftlDirFile = FileUtils.getFile(generatorProperty.getTemplatePath(), conf.templateClassify);
            }
        } else {
            ftlDirFile = FileUtils.getFile(conf.templatePath, conf.templateClassify);
        }
        //
        //
        JSONObject packageObject = JSON.parseObject(JSON.toJSONString(conf.packageConf));
        for (FTLDataModel ftlDataModel : modelList) {
            JSONObject dataModel = FreemarkerUtil.ftlDataModelToJSONObject(ftlDataModel);
            dataModel.putAll(packageObject);
            if (conf.variables != null) dataModel.putAll(conf.variables);
            //
            TableInfo tableInfo = ftlDataModel.getTableInfo();
            List<CodeTemplateMapping> mappingList = this.initCodeTemplateMapping(tableInfo, conf.getPathConf());
            this.writeFtlMappingList(ftlDirFile, mappingList, dataModel);
        }
    }

    /**
     * 获取模板数据
     *
     * @param conf
     * @param paramList
     * @param tableColumnMap
     * @return
     * @throws Exception
     */
    protected List<FTLDataModel> getAllTableDataModel(ProjectConf conf, List<CodeGenParam> paramList, Map<String, List<TableColumn>> tableColumnMap) throws Exception {
        if (paramList == null || paramList.isEmpty()) return Collections.emptyList();
        List<FTLDataModel> list = new ArrayList<>();

        DatabaseMetaData databaseMetaData = databaseUtil.getDatabaseMetaData();

        for (CodeGenParam param : paramList) {
            //
            PackageConf packageConf = conf.getPackageConf();
            String tableComment = param.getTableComment();
            String tableName = param.getTableName();
            String modelName = param.getModelName();
            String relationColumn = param.getRelationColumn();
            if (StringUtils.isNotBlank(relationColumn)) relationColumn = relationColumn.toUpperCase();
            //
            TableInfo tableInfo = TableColumnUtil.getTableInfo(tableName, tableComment, modelName);
            tableInfo.setRelationColumn(relationColumn);
            List<TableColumn> columnList = tableColumnMap.get(tableName);

            if (StringUtils.isNotBlank(tableName) && CollectionUtils.isEmpty(columnList)) throw new Exception(tableName + " 表字段列表查询为空");
            List<String> varNameList = columnList.stream().map(TableColumn::getVarName).collect(Collectors.toList());
            FTLDataModel ftlDataModel = new FTLDataModel();
            //
            ftlDataModel.setDatabaseName(databaseMetaData.getDatabaseProductName());
            ftlDataModel.setDatabaseVersion(databaseMetaData.getDatabaseProductVersion());
            ftlDataModel.setTableInfo(tableInfo);
            ftlDataModel.setColumnList(columnList);
            ftlDataModel.setVarNameList(varNameList);
            //
            ftlDataModel.setDate(conf.getDate());
            ftlDataModel.setAuthor(conf.getAuthor());
            ftlDataModel.setBaseRequestMapping(StringUtils.defaultString(param.getModuleApi()));//根路径默认=/api/v1/
            ftlDataModel.setModelReference(packageConf.getModel() + "." + tableInfo.getClassName());
            ftlDataModel.setMapperReference(packageConf.getMapper() + "." + tableInfo.getClassName() + "Mapper");
            //
            list.add(ftlDataModel);
        }
        return list;
    }

    /**
     * 初始化模板映射关系
     *
     * @param tableInfo
     */
    private List<CodeTemplateMapping> initCodeTemplateMapping(TableInfo tableInfo, WritePathConf pathConf) {
        String className = tableInfo.getClassName();
        List<CodeTemplateMapping> list = new LinkedList<>();
        list.add(CodeTemplateMapping.of("Controller.ftl", className + "Controller.java", pathConf.getController()));
        list.add(CodeTemplateMapping.of("Service.ftl", className + "Service.java", pathConf.getService()));
        list.add(CodeTemplateMapping.of("ServiceImpl.ftl", className + "ServiceImpl.java", pathConf.getServiceImpl()));
        list.add(CodeTemplateMapping.of("ModelEntity.ftl", className + ".java", pathConf.getModelEntity()));
        list.add(CodeTemplateMapping.of("MapperJava.ftl", className + "Mapper.java", pathConf.getMapperJava()));
        list.add(CodeTemplateMapping.of("MapperXml.ftl", className + "Mapper.xml", pathConf.getMapperXml()));
        list.add(CodeTemplateMapping.of("VOEntity.ftl", className + "EntityVO.java", pathConf.getVoEntity()));
        list.add(CodeTemplateMapping.of("VORequest.ftl", className + "RequestVO.java", pathConf.getVoRequest()));
        list.add(CodeTemplateMapping.of("VOResponse.ftl", className + "ResponseVO.java", pathConf.getVoResponse()));
        return list;
    }

    /**
     * 根据模板关系列表保存代码文件
     *
     * @param ftlDirFile  Freemarker模板目录
     * @param mappingList Freemarker模板和代码关系列表
     * @param dataModel   Freemarker模型数据
     */
    private void writeFtlMappingList(File ftlDirFile, List<CodeTemplateMapping> mappingList, JSONObject dataModel) throws Exception {
        for (CodeTemplateMapping mapping : mappingList) {
            String ftlName = mapping.getTemplateName();
            String filePath = mapping.getFilePath();
            if (StringUtils.isBlank(filePath)) continue;
            File ftlFile = FileUtils.getFile(ftlDirFile, ftlName);
            if (!ftlFile.exists()) continue;
            //
            File writeFile = FileUtils.getFile(filePath, mapping.getFileName());
            if (!writeFile.getParentFile().exists()) writeFile.getParentFile().mkdirs();
            codeGenerator.generateCodeToFile(ftlFile, writeFile, dataModel);
        }
    }
}
